分享好友 技术首页 技术分类 切换频道

ARM-Linux 中断分析

2020-07-18 06:28100

ARM体系结构中,把复位、中断、快速中断等都看作‘ARM-Linux异常’,当这些‘异常’发生时,CPU会到固定地址处去找指令,他们对应的地址如下:
地址 异常类型 进入时的工作模式
0x00000000 Reset Supervisor
0x00000004 Und Undefined
0x00000008 Soft interupt Supervisor
0x0000000c Abort(prefetch) Abort
0x00000010 Abort(data) Abort
0x00000014 Reserved Reserved
0x00000018 IRQ IRQ
0x0000001c FIQ FIQ
首先要明确的一点就是,无论内存地址空间是如何ARM-Linux映射的,以上这些地址都不会变,比如当有快速中断发生时,ARM将铁定到0X0000001C这个地址处取指令。这也是BOOTLOADER把操作系统引导以后,内存必须重映射的原因!否则操作系统不能真正接管整套系统!
LINUX启动以后要初始化这些区域,初始化代码在main.c中ARM-Linux的start_kernel()中,具体是调用函数trap_ini()来实现的。如下面所示(具体可参照entry-armv.S):
.LCvectors: swi SYS_ERROR0
b __real_stubs_start + (vector_undefinstr - __stubs_start)
ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
b __real_stubs_start + (vector_prefetch - __stubs_start)
b __real_stubs_start + (vector_data - __stubs_start)
b __real_stubs_start + (vector_addrexcptn - __stubs_start)
b __real_stubs_start + (vector_IRQ - __stubs_start)
b __real_stubs_start + (vector_FIQ - __stubs_start)

ENTRY(__trap_init)
stmfd ARM-Linux sp!, {r4 - r6, lr}

adr r1, .LCvectors @ set up the vectors
ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}ARM-Linux

add r2, r0, #0x200
adr r0, __stubs_start @ copy stubs to 0x200
adr r1, __stubs_end
1: ldr r3, [r0], #4ARM-Linux
str r3, [r2], #4
cmp r0, r1
blt 1b
LOADREGS(fd, sp!, {r4 - r6, pc})

以上可以看出这个函数初始化了中断向量,实际ARM-Linux上把相应的跳转指令拷贝到了对应的地址。
当发生中断时,不管是从ARM-Linux用户模式还是管理模式调用的,zui终都要调用do_IRQ():
__irq_usr: sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ save r0 - r12
ldr r4, .LCirq
add r8, sp, #S_PC
ldmia r4, {r5 - r7} @ get saved PC, SPSR
stmia r8, {r5 - r7} ARM-Linux @ save pc, psr, old_r0
stmdb r8, {sp, lr}^
alignment_trap r4, r7, __temp_irq
zero_fp
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
adrsvc ne, lr, 1b
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
bne do_IRQ @ 调用do_IRQ来实现具体的中断ARM-Linux处理
mov why, #0
get_current_task tsk
b ret_to_user

对于以上代码,在很多文章中都有过分析,这里不再赘述。
ARM-Linux

Linux每个中断通过一个结构irqdesc来描述,各中断的信息都在这个结构中得以ARM-Linux体现:
struct irqdesc {
unsigned int nomask : 1; /* IRQ does not mask in IRQ */
unsigned int enabled : 1; /* IRQ is currently enabled */
unsigned int triggered: 1; /* IRQ has occurred */
unsigned int probing : 1; /* IRQ in use for a probe */
unsigned int probe_ok : 1; ARM-Linux /* IRQ can be used for probe */
unsigned int valid : 1; /* IRQ claimable */
unsigned int noautoenable : 1; /* don"t automatically enable IRQ */
unsigned int unused :25;
void (*mask_ack)(unsigned int irq); /* Mask and acknowledge IRQ */
void (*mask)(unsigned int irq); /* Mask IRQ */
void (*unmask)(unsigned int irq); /* Unmask IRQ */
struct irqaction *action;
/*ARM-Linux
* IRQ lock detection
*/
unsigned int lck_cnt;
unsigned int lck_pc;
unsigned int lck_jif;
};

在具体ARM-Linux的ARM芯片中会有很多的中断类型,每一种类型的中断用以上结构来表示:
struct irqdesc irq_desc[NR_IRQS]; /* NR_IRQS根据不同的MCU会有所区别*/
在通过request_irq()函数注册中断服务程序的时候ARM-Linux,将会把中断向量和中断服务程序对应起来。
我们来看一下request_irq的源码:
int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),
unsigned long irq_flags, const char * devname, void *dev_id)
{
unsigned long retval;
struct irqaction *action;ARM-Linux

if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||
(irq_flags & SA_SHIRQ && !dev_id))
return -EINVAL;
action = (struct irqaction *)kmallocARM-Linux(sizeof(struct irqaction), GFP_KERNEL);
if (!action) /* 生成action结构*/
return -ENOMEM;

action->handler = handler;
action->flags = irq_flags;ARM-Linux
action->mask = 0;
action->name = devname;
action->next = NULL;
action->dev_id = dev_id;

retval = setup_arm_irq(irq, action); /*把中断号irq和action 对应ARM-Linux起来*/

if (retval)
kfree(action);ARM-Linux
return retval;
}
其中*个参数irq就是中断向量,第二个参数即是要注册的中断服务程序。很多同仁可能疑惑的是,我们要注册的中断向量号是怎么确定的呢?这要根据具体芯片的中断控制器,比如三星的S3C2410,需要 通过读取其中的中断状态寄存器,来获得是哪个设备发生了中断:
ARM-Linux

if defined(CONFIG_ARCH_S3C2410)
#Include

.macro disable_fiq
.endm
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
mov r4, #INTBASE @ virtual address of IRQ registers
ldr \irqnr,ARM-Linux [r4, #0x8] @ read INTMSK 中断掩码寄存器
ldr \irqstat, [r4, #0x10] @ read INTPND 中断寄存器
bics \irqstat, \irqstat, \irqnr
bics \irqstat, \irqstat, \irqnr
beq 1002f
mov \irqnr, #0ARM-Linux
1001: tst \irqstat, #1
bne 1002f @ found IRQ
add \irqnr, \irqnr, #1
mov \irqstat, \irqstat, lsr #1
cmp \irqnr, #32
bcc 1001b
1002:
.endm
.macro irq_prio_table
.endm
以上代码也告诉了ARM-Linux我们,中断号的确定,其实是和S3C2410手册中SRCPND寄存器是一致的,即:

/* Interrupt Controller */
#define IRQ_EINT0 0 /* External interrupt 0 */
#define IRQ_EINT1 1 /* External interrupt 1 */
#define IRQ_EINT2 2 /* External interrupt 2 */
#define IRQ_EINT3 3 ARM-Linux /* External interrupt 3 */
#define IRQ_EINT4_7 4 /* External interrupt 4 ~ 7 */
#define IRQ_EINT8_23 5 /* External interrupt 8 ~ 23 */
#define IRQ_RESERVED6 6 /* Reserved for future use */
#define IRQ_BAT_FLT 7
#define IRQ_TICK 8 /* RTC time tick interrupt */
#define IRQ_WDT 9 /* Watch-Dog timer interrupt */
#define IRQ_TIMER0 10 /* Timer 0 interrupt */
#define IRQ_TIMER1 11 /* Timer 1 interrupt */ARM-Linux
#define IRQ_TIMER2 12 ARM-Linux /* Timer 2 interrupt */
#define IRQ_TIMER3 13 /* Timer 3 interrupt */
#define IRQ_TIMER4 14 /* Timer 4 interrupt */
#define IRQ_UART2 15 /* UART 2 interrupt */
#define IRQ_LCD 16 /* reserved for future use */
#define IRQ_DMA0 17 /* DMA channel 0 interrupt */
#define IRQ_DMA1 18 /* DMA channel 1 interrupt */
#define IRQ_DMA2 19 /* DMA channel 2 interrupt */ARM-Linux
#define IRQ_DMA3 20 /* DMA channel 3 interrupt */
#define IRQ_SDI 21 ARM-Linux /* SD Interface interrupt */
#define IRQ_SPI0 22 /* SPI interrupt */
#define IRQ_UART1 23 /* UART1 receive interrupt */
#define IRQ_RESERVED24 24
#define IRQ_USBD 25 /* USB device interrupt */
#define IRQ_USBH 26 /* USB host interrupt */
#define IRQ_IIC 27 /* IIC interrupt */
#define IRQ_UART0 28 /* UART0 transmit interrupt */
#define IRQ_SPI1 29 /* UART1 transmit interrupt */
#define IRQ_RTC 30 /* RTC alarm interrupt */
#define IRQ_ADCTC 31 /* ADC EOC interrupt */
#define NORMAL_IRQ_OFFSET 32
这些宏定义在文件irqs.h中,大家ARM-Linux可以看到它的定义取自S3C2410的文档。
总结: linux在初始化的时候已经把每个中断向量的地址准备好了!就是说添加中断服务程序的框架已经给出,当某个中断发生时,将会到确定的地址处去找指令,所以我们做驱动程序时,只需要经过request_irq()来挂接自己编写的中断服务ARM-Linux程序即可。

另:对于快速中断,linux在初始化时是空的,所以要对它挂接中断处理程序,就需要单独的函数set_fiq_handler()来实现,此函数在源文件fiq.c中,有兴趣的读者可进一步ARM-Linux研究。

免责声明:矿库网文章内容来源于网络,为了传递信息,我们转载部分内容,尊重原作者的版权。所有转载文章仅用于学习和交流之目的,并非商业用途。如有侵权,请及时联系我们删除。感谢您的理解与支持。

点赞 0
举报
收藏 0
评论 0
分享 1
关于步进电机驱动器的细分原理

0评论2020-07-1928

共轭曲面的数字化方法及共轭鼓形齿联轴器传动研究(二)

0评论2020-07-1817

液晶人机界面下向量图显示的设计与应用

0评论2020-07-1812

台湾隆兴LS600变频器

0评论2020-07-1815

ARM嵌入式系统中断向量表的动态配置

0评论2020-07-1812

什么样的处理器会嵌入式行业未来

0评论2020-07-1716

步进电机多级细分驱动方法研究

0评论2020-07-1717

《汉英词典》机械类常用词汇(5)

0评论2020-07-1616

如何用最简单的方法将斯派超科技设备升级为油液监测平台

0评论2020-07-1015